Structs
-
Unlike with tuples, in a struct you’ll name each piece of data so it’s clear what the values mean. Adding these names means that structs are more flexible than tuples: you don’t have to rely on the order of the data to specify or access the values of an instance.
Creation
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
-
Tuple Structs :
struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); }
Creating methods
-
The struct methods' first parameter is always
self, which represents the instance of the struct the method is being called on.-
The
&selfis actually short forself: &Self.-
The type
Selfis an alias for the type that theimplblock is for.
-
-
Methods can take ownership of
self, borrowselfimmutably, as we’ve done here, or borrowselfmutably, just as they can any other parameter. -
We chose
&selfhere, as we don’t want to take ownership, and we just want to read the data in the struct, not write to it.
-
-
All functions defined within an
implblock are called associated functions because they’re associated with the type named after theimpl.-
Everything within this
implblock will be associated with theRectangletype.
-
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
-
Using multiple
impl-
There’s no reason to separate these methods into multiple
implblocks here, but this is valid syntax:
impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } -
Creating an instance
-
Directly :
let user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; -
"Builder" :
fn build_user(email: String, username: String) -> User { User { active: true, username: username, // can be written just 'username' email: email, // can be written just 'email' sign_in_count: 1, } } -
Constructors :
-
Associated functions that aren’t methods are often used for constructors that will return a new instance of the struct.
-
These are often called
new, butnewisn’t a special name and isn’t built into the language. -
For example, we could choose to provide an associated function named
squarethat would have one dimension parameter and use that as both width and height, thus making it easier to create a squareRectanglerather than having to specify the same value twice
-
impl Rectangle { fn square(size: u32) -> Self { Self { width: size, height: size, } } }-
The
Selfkeywords in the return type and in the body of the function are aliases for the type that appears after theimplkeyword, which in this case isRectangle.let sq = Rectangle::square(3);
-
Access
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
-
Note that the entire instance must be mutable; Rust doesn’t allow us to mark only certain fields as mutable.
-
No need for -> access operator :
-
Rust automatically adds in
&,&mut, or*soobjectmatches the signature of the method.
(&p1).distance(&p2); // Same thing p1.distance(&p2); -